package de.jonasjelten.jcalc.mathExps;

import de.jonasjelten.jcalc.math.JThreadGroup;
import de.jonasjelten.jcalc.api.JCalc;
import de.jonasjelten.jcalc.exception.JEvaluationException;
import de.jonasjelten.jcalc.math.JNumber;
import de.jonasjelten.jcalc.math.JPreferences;
import java.util.ArrayList;

/**
 *
 * @author Michael Enßlin
 */
public abstract class JMathExp extends Thread {

    protected JNumber evalResult;
    public String name;
    private boolean calcstarted;

    public abstract JNumber eval() throws JEvaluationException;

    @Override
    public abstract String toString();

    public abstract int getNonTrivialChildCount();

    public int getNonTrivialMathExpsCount() {
        if (isTrivial()) {
            return getNonTrivialChildCount();
        } else {
            return getNonTrivialChildCount() + 1;
        }
    }

    /*
     * @autor michael
     *
     * @return The boolean return value decides if the JMathExp is counted in
     * the progress bar and outputted as a intermediate result in the ownerWindow.
     * i.e., JNumber is trivial while JFac is not trivial.
     */
    public abstract boolean isTrivial();
    public JCalc jcalc;

    public JMathExp(JCalc jcalc) {
        super(jcalc.expgroup, "JMathExp");
        this.jcalc = jcalc;
        this.calcstarted = false;
    }

    /**
     * Diese Methode startet die Mathexpression
     * Wenn das Threadlimit erreicht ist, wird die Aufgabe ohne einen neuen
     * Thread zu starten erledigt.
     * @return ist ein Thread gestartet worden
     */
    public boolean init() {
        if (calcstarted == false) {
            calcstarted = true;
            if (JPreferences.usenothreadlimit || jcalc.expgroup.getActiveThreads() < JPreferences.allthreadmax) {
                this.start(); //Start as Thread
                return true;
            } else {
                this.run(); // Run as normal without threading
            }
        } else {
            try {
                System.err.println(this.getClass().toString() + " execuded init() again. ");
                throw new Exception("Init of the JMathExp was execuded again.");
            } catch (Exception e) {
                e.printStackTrace(System.err);
            }
        }
        return false;
    }

    @Override
    public void run() {
        boolean noerror = true;
        name = toString();
        setName(name); // Setzt den Threadname auf die Mathexp, die berechnet wird

        JMathExp incalc = null;
        if (JPreferences.cache) {
            incalc = jcalc.expgroup.calculating.getValue(name);
        }
        JNumber cache = null;
        if (incalc != null) {
            evalResult = incalc.getEvalResult();
        } else {
            if (JPreferences.cache) {
                cache = jcalc.expgroup.cache.getValue(name);
            }
            if (cache == null || (this instanceof JConst && JPreferences.cacheconstants)) {
                if (!isTrivial()) {
                    jcalc.echoProgress("Evaluating " + name);
                }
                try {
                    if (JPreferences.cache) {
                        jcalc.expgroup.calculating.insertValue(name, this); //Report beginning of calculation
                    }

                    evalResult = eval(); // Calculate the MathExpression

                    if ((((this instanceof JConst) && JPreferences.cacheconstants) || !(this instanceof JConst)) && JPreferences.cache) {
                        jcalc.expgroup.cache.insertValue(toString(), evalResult); //Save Result to Cache
                    }
                    if (JPreferences.cache) {
                        jcalc.expgroup.calculating.removeValue(name); //Report completion of calculation
                    }
                } catch (JEvaluationException e) {
                    jcalc.errorMessage("JMathExp evaluation error: " + e.toString() + " with message " + e.getMessage());
                    e.printStackTrace(System.err);
                    noerror = false;
                }

            } else {
                evalResult = cache;
            }
        }
        if (!isTrivial() && noerror) {
            jcalc.updateProgressOnMathExpFinished(this, incalc != null || cache != null);
        }

    }

    //the deadlock was:   the run method above is not finished at calling updateprogress
    //so the getEvalResult() in updateProgressOnMathExpFinished will wait for the running thread
    //this results in a deadlock.
    /**
     * this method provides information without deadlocking cause of join()
     * @return the thread result.
     */
    public JNumber forceResult() {
        return evalResult;
    }

    /**
     * this method is essential for other mathexps
     * it waits for itself to finish the calculation
     * then it returns the result
     * @return the calculated result
     */
    public JNumber getEvalResult() {
        try {
            this.join();
        } catch (InterruptedException ex) {
            System.out.println("Exception: " + ex);
        }
        return evalResult;
    }
}
